大家在日常生活中,應該看過滿多表單的某個欄位會隨著另個欄位的改變,而造成該欄位的驗證邏輯需要改變的情況吧?
舉例來說,可能會有個欄位叫做聯絡資訊,使用者可以選擇要填入手機號碼或者是 E-mail ,該欄位再根據使用所選擇的類型來檢核該欄位的值。
今天,我們就來用 Reactive Forms 實作這個欄位,而這個欄位我會實作在我們的被保人表單上,各位就隨意吧!
如果已經忘記被保人表單長怎麼樣的話,可以先回頭複習一下第十一天的文章:Reactive Forms 實作 - 動態表單初體驗。
首先,我們需要在原本的被保人表單裡新增一個欄位:聯絡資訊。
HTML 的部份大概會長這樣:
<p>
<label>聯絡資訊:</label>
</p>
<p>
<select>
<option value="">請選擇</option>
<option value="mobile">手機</option>
<option value="email">E-Mail</option>
</select>
<input type="text">
</p>
畫面看起來會像這樣:
雖然聯絡資訊是一個欄位,但其實我們需要兩個 FormControl
,一個給下拉選單,一個給實際填值的 input
元素。
因此,我們要在原本的 createInsuredFormGroup
裡多加兩個欄位,像是這樣:
private createInsuredFormGroup(): FormGroup {
return this.formBuilder.group({
name: [
'',
[Validators.required, Validators.minLength(2), Validators.maxLength(10)]
],
gender: ['', Validators.required],
age: ['', Validators.required],
contactInfoType: ['', Validators.required],
contactInfo: ['', Validators.required]
});
}
然後將剛剛新增的欄位與畫面的元素綁定:
<p>
<select formControlName="contactInfoType">
<option value="">請選擇</option>
<option value="mobile">手機</option>
<option value="email">E-Mail</option>
</select>
<input type="text" formControlName="contactInfo">
</p>
接著我們透過把資料印在畫面上的方式來檢查是否已正確綁定,像這樣:
<pre>{{ formGroup?.getRawValue() | json }}</pre>
結果:
看起來已經有正確跟畫面上的元素綁定了,那接下來要怎麼做才好呢?
FormControl 的父類別 AbstractControl 有個屬性叫做 valueChanges ,它是一個 Observable 。
我們可以透過訂閱某個 AbstractControl 的 valueChanges 這個 Observable 來知道該欄位是否已經發生變化,並且做出相應的處理。
因此,我們可以這樣調整 createInsuredFormGroup
裡的實作:
private createInsuredFormGroup(): FormGroup {
const contactInfoTypeControl = this.formBuilder.control('', Validators.required);
const contactInfoControl = this.formBuilder.control('', Validators.required);
contactInfoTypeControl.valueChanges.subscribe((value) => {
switch (value) {
case 'mobile':
contactInfoControl.setValidators([Validators.required, Validators.pattern(/^09\d{8}$/)]);
break;
case 'email':
contactInfoControl.setValidators([Validators.required, Validators.email]);
break;
default:
contactInfoControl.setValidators([Validators.required]);
break;
}
contactInfoControl.updateValueAndValidity();
});
return this.formBuilder.group({
name: [
'',
[Validators.required, Validators.minLength(2), Validators.maxLength(10)]
],
gender: ['', Validators.required],
age: ['', Validators.required],
contactInfoType: contactInfoTypeControl,
contactInfo: contactInfoControl
});
}
上述程式碼中有以下三個要點:
建立 FormControl
的時候可以藉由 this.formBuilder.control()
的方式建立,也可以直接使用 new FormControl()
建立,這點在前面的文章已經有提過,不過我在這邊再提醒大家一次。
setValidators()
執行完後,記得一定要使用 updateValueAndValidity()
來更新當前欄位的驗證,不然就要等到該欄位的值有改變時才會以新的驗證器來驗證。
由於 contactInfoType
允許使用者選擇 請選擇
的選項,因此記得在 default
的區塊裡,將 Validators.required
給加回去。
這邊改好之後,我們也順便調整一下 getErrorMessage
的實作,讓使用者可以知道該欄位的驗證有誤:
getErrorMessage(key: string, index: number): string {
const formGroup = this.formArray.controls[index];
const formControl = formGroup.get(key);
let errorMessage: string;
if (!formControl || !formControl.errors || formControl.pristine) {
errorMessage = '';
} else if (formControl.errors.required) {
errorMessage = '此欄位必填';
} else if (formControl.errors.minlength) {
errorMessage = '姓名至少需兩個字以上';
} else if (formControl.errors.maxlength) {
errorMessage = '姓名至多只能輸入十個字';
// 增加以下兩個判斷
} else if (formControl.errors.pattern) {
errorMessage = '手機號碼格式錯誤';
} else if (formControl.errors.email) {
errorMessage = 'E-mail 格式錯誤';
}
return errorMessage!;
}
這邊要提醒大家的是,由於驗證 E-mail 格式的方式我今天是用 Validators.email
的驗證器來驗,不是之前的 Validators.pattern()
,所以我可以直接用 formControl.errors.email
來判斷。
如果實作時,手機號碼跟 E-mail 都是用 Validators.pattern()
的驗證器來驗的話,就需要進一步去比對 formControl.errors.pattern
裡的 Regular Expression 來分辨究竟是手機號碼的格式錯誤還是 E-mail 的格式錯誤了。
像是這樣:
} else if (formControl.errors.pattern) {
const requiredPattern = formControl.errors.pattern.requiredPattern;
if (requiredPattern === '/A Regular Expression/') {
errorMessage = '手機號碼格式錯誤';
} else if (requiredPattern === '/B Regular Expression/') {
errorMessage = 'E-mail 格式錯誤';
}
}
如此一來,我們就完成這個欄位的功能囉!
結果:
今天的重點是學會如何使用 valueChanges 來動態調整相關欄位的驗證邏輯。
雖然是 Observable 是 RxJS 的東西,但今天並沒有太艱難或太複雜的運用,使用上的感覺會跟使用 Promise 的感覺類似,不過我個人認為 RxJS 好玩且強大許多。
關於 RxJS ,如果大家想知道更多資訊,我推薦大家去看 Mike 的打通 RxJS 任督二脈系列文,或者是直接買實體書也行。
雖然今天的實作已經完成了,但因為有調整程式碼的關係,測試程式碼其實也需要相應的調整才不會出錯,此部份就交給大家實作我就不再用篇幅分享實作囉!
今天的程式碼會放在 Github - Branch: day23 上供大家參考,建議大家在看我的實作之前,先按照需求規格自己做一遍,之後再跟我的對照,看看自己的實作跟我的實作不同的地方在哪裡、有什麼好處與壞處,如此反覆咀嚼消化後,我相信你一定可以進步地非常快!
如果有任何的問題或是回饋,還請麻煩留言給我讓我知道!
Hi Leo,
我在驗證電話號碼的程式碼上會驗證失敗
示意圖:
程式碼中的$^好像打反了
噗,真的打反了!感謝糾正!!